Skip to content

线性回归

什么是线性回归

回归(regression)是能为一个或多个自变量与因变量之间关系建模的一类方法。在自然科学和社会科学领域,回归经常用来表示输入和输出之间的关系

线性回归(Linear Regression)是一种用于研究因变量(y)和一个或多个自变量(x)之间的线性关系的统计方法

线性回归的形式

简单线性回归(只有一个变量)

基本公式:

y=wx+b
  • y:目标值(因变量)
  • x:输入特征(自变量)
  • w:斜率(权重,weight)
  • b:截距项(bias)

多元线性回归(多个输入变量)

基本公式:

y=w1x1+w2x2++wnxn+b=wTx+b
  • x=[x1,x2,,xn]T: 特征向量
  • w=[w1,w2,,wn]T: 权重向量

线性回归的目标:最小化误差

训练线性回归的目的是让模型的预测值 y^ 尽可能接近真实值 y

y^i=wTxi+b

损失函数:均方误差(MSE)

J(w,b)=1mi=1m(y^iyi)2
  • m:样本数量
  • y^i:第 i 个样本第预测值
  • yi:真实值

我们希望平方误差的平均值越小越好

训练线性回归模型

梯度下降(Gradient Descent)

通过迭代调整 wb 来最小化损失函数

  1. 随机初始化 wb
  2. 重复以下过程直到收敛(即找到最小损失函数)
    • 计算梯度(对 wb
    • 更新参数:
w:=wηJw,b:=bηJb

确定一个好的学习率

  • 学习率太大:步子太大,可能跳过最优解,甚至导致损失函数发散(误差越来越大)
  • 学习率太小:步子太小,学习速度慢,可能需要很多次迭代才能收敛,甚至卡在局部最优解

从一个小的学习率开始

通常可以从一个较小的学习率开始尝试,比如 0.01 或 0.001,然后根据训练情况调整。这是一个安全的策略,避免一开始就发散

可能尝试从 0.001 开始,每次逐渐递增三倍,观察学习曲线

画出学习曲线

通过学习曲线(损失函数)来观察是否稳定下降,如果曲线平滑下降,学习率可能比较合适;如果曲线震荡或者上升,学习率可能太大;如果下降很慢,则说明太小

特征缩放

特征缩放(包括归一化和标准化)是一种数据预处理方法,目的是让不同特征的数值范围变得相似,避免某些特征因为数值范围大而在模型中占据主导地位

为什么要使用特征缩放

梯度下降法对特征的数值范围非常敏感。如果特征的范围不一致,梯度的大小会差异很大,导致权重更新时“步子”不均匀,有的特征更新快,有的慢,模型可能需要更多迭代才能收敛,甚至可能无法收敛

什么时候需要进行特征缩放

通常情况下建议对所有数据进行特征缩放,以保证模型训练的公平性和稳定性,但如果特征范围本来就相似,或者算法对范围不敏感,可以不缩放

算法

最小 - 最大归一化

此算法的范围是 [0,1] 或者 [1,1]

x=xxminxmaxxmin

标准化(Standardization, Z - Score Scaling)

X=Xμσ

这里 X 是原始特征值,μ 是该特征的均值,σ 是标准差

手写一个简单线性回归

需要使用两个库,numpymatplotlib,一个用来做数学计算,另外一个用来画图,做可视化

python
# 导入基础库并且生成模拟数据
import numpy as np
import matplotlib.pyplot as plt

使用 numpy 来生成一些模拟数据,生成用于模拟的数据集

python
# 生成模拟数据
np.random.seed(0) # 设置随机种子
X = np.random.rand(100, 1) # 100个样本,一个特征
y = 2 * X + np.random.rand(100, 1) * 0.6 # 高斯噪声,随机点生成

之后来初始化和定义用于计算的一些参数和函数,下面函数的一些概念,均在上面基础篇上出现过

python
# 初始化参数
w = np.random.randn() # 初始化一个随机的w数值,即刚开始的随机变量
b = np.random.randn()

# 学习率
lr = 0.01

# 损失函数:均方误差(ESM)
def compute_loss(y_pred, y_true):
    return np.mean((y_pred - y_true) ** 2) # 使用预测减去实际的计算损失

# 预测函数
def predict(X):
    return w * X + b # 使用原来的参数进行预测

# 梯度计算,直到收敛
def compute_gradients(X, y, y_pred):
    n = len(X)  # 样本数量
    dw = (2/n) * np.sum((y_pred - y) * X)  # 对w的梯度
    db = (2/n) * np.sum(y_pred - y)        # 对b的梯度
    return dw, db

正式开始训练模型

python
# 训练模型
epochs = 3000  # 确定训练代数
for epoch in range(epochs):
    y_pred = predict(X)  # 确定预测的y数值
    loss = compute_loss(y_pred, y)  # 计算损失
    dw, db = compute_gradients(X, y, y_pred)  # 计算梯度

    # 更新参数
    w -= lr * dw
    b -= lr * db

    # 每100次打印一次参数
    if epoch % 100 == 0:
        print(f"当前迭代 {epoch}:Loss = {loss}, w = {w}, b = {b}")

这里的更新参数遵从:

w:=wηJw,b:=bηJb

在前面使用 compute_gradients 函数对其进行了梯度计算

python
dw = (2/n) * np.sum((y_pred - y) * X)

这个 np.sum (...) 就是把每个样本的梯度项全部加起来(累加),最终得到平均的梯度

最终训练的结果为

当前迭代 0:Loss = 3.360457010379737, w = 1.1451367960151249, b = -1.0437374740069902
当前迭代 100:Loss = 0.05000333971000506, w = 1.8210078929333788, b = 0.2606548461628321
当前迭代 200:Loss = 0.028025721164853103, w = 1.8852749691368647, b = 0.36186703590328345
当前迭代 300:Loss = 0.02777880142277748, w = 1.8986843397860884, b = 0.36593878510327527
当前迭代 400:Loss = 0.027699259584641443, w = 1.9069340963491677, b = 0.36264078935988026
当前迭代 500:Loss = 0.02763916198454221, w = 1.9138655152547173, b = 0.35919983431381447
当前迭代 600:Loss = 0.027593215478616283, w = 1.9199035531556587, b = 0.35614573793057736
当前迭代 700:Loss = 0.027558084323772734, w = 1.9251814722129978, b = 0.35347149826393925
当前迭代 800:Loss = 0.027531222670007408, w = 1.9297964397055116, b = 0.35113279056033386
当前迭代 900:Loss = 0.027510683964356013, w = 1.9338318480268912, b = 0.3490877531373423
当前迭代 1000:Loss = 0.027494979851960415, w = 1.9373604894195964, b = 0.3472995292155738
当前迭代 1100:Loss = 0.027482972320895915, w = 1.9404460045107752, b = 0.34573587000715716
当前迭代 1200:Loss = 0.027473791235162914, w = 1.943144041104862, b = 0.3443685748584561
当前迭代 1300:Loss = 0.02746677127953805, w = 1.9455032586954581, b = 0.3431729844142777
当前迭代 1400:Loss = 0.027461403746938083, w = 1.9475662061191967, b = 0.3421275360263249
当前迭代 1500:Loss = 0.02745729967452634, w = 1.9493700889361771, b = 0.3412133748793251
当前迭代 1600:Loss = 0.02745416165718836, w = 1.950947440438186, b = 0.3404140139426129
当前迭代 1700:Loss = 0.027451762295816154, w = 1.9523267083914417, b = 0.3397150366241853
当前迭代 1800:Loss = 0.02744992771863752, w = 1.9535327680962429, b = 0.33910383676535083
当前迭代 1900:Loss = 0.027448524981449462, w = 1.9545873710166628, b = 0.338569391286805
当前迭代 2000:Loss = 0.02744745243370295, w = 1.9555095370714028, b = 0.3381020613857766
当前迭代 2100:Loss = 0.027446632352308983, w = 1.9563158976608237, b = 0.33769341869880004
当前迭代 2200:Loss = 0.027446005309368537, w = 1.957020995616696, b = 0.3373360932949432
当前迭代 2300:Loss = 0.027445525865679117, w = 1.9576375474843057, b = 0.3370236407580188
当前迭代 2400:Loss = 0.027445159277944046, w = 1.958176672867222, b = 0.3367504259605912
...
当前迭代 2600:Loss = 0.02744466466310397, w = 1.9590603159625233, b = 0.33630261849885573
当前迭代 2700:Loss = 0.027444500793311913, w = 1.9594207703080337, b = 0.33611994956114444
当前迭代 2800:Loss = 0.027444375496729134, w = 1.9597359588546208, b = 0.3359602201593469
当前迭代 2900:Loss = 0.02744427969363169, w = 1.960011566074055, b = 0.3358205495502205

下面进行可视化

python
# 可视化
plt.scatter(X, y, label='Data')
plt.plot(X, predict(X), color='red', label='Fitted Line')
plt.legend()
plt.title("Simple Linear Regression")
plt.xlabel("x")
plt.ylabel("y")
plt.show()

Q&A

如何确定学习率(Lr)

  1. 一般来说按照经验值,从 0.1 和 0.01 开始
  2. 调参实验:试几个值比如 0.001、0.01、0.1、0.5 比较 loss 收敛情况。
  3. 画出 loss 曲线:观察 loss 曲线的趋势是否收敛平稳
  4. 学习率衰减(进阶):开始时大、逐渐减小

如何确定要迭代(Epochs)多少次

  1. 看 Loss 是否还在下降,如果 loss 曲线已经趋于平稳,说明训练快结束了
  2. 设置最大值,如 1000 次,人工设一个上限,比如 100、500、1000
  3. 提前停止(Early Stopping),如果连续 N 次迭代 loss 没下降,就提前结束
  4. 使用验证集监控过拟合,如果在验证集上效果开始变差,就说明模型训练太久了

统计学中的线性回归

本质上,统计学中的线性回归公式计算斜率和截距与机器学习中的方法是相同的。两者都依赖于最小二乘法 (Ordinary Least Squares, OLS) 来找到最佳拟合直线

统计学中的方程通常为:

Y=β0+β1X+ϵ

其中的系数可以很方便的使用公式法求解出来:

β0=XYnX¯Y¯X2nX¯2β1=Y¯β0X¯

为什么机器学习中不使用公式法求解精确解

CAUTION

以下解答由 Gemini 2.5 Pro 生成,仅供参考

机器学习中并非不能使用公式法(通常指正规方程,Normal Equation)求解线性回归的参数(斜率和截距),而是在某些情况下,公式法不是最优或最实用的选择,因此会采用迭代方法如梯度下降。

以下是为什么机器学习中不总是或不优先使用公式法求解线性回归的原因:

  1. 计算成本过高,尤其是在特征数量巨大时 (High Number of Features):

    • 正规方程 (Normal Equation): 线性回归的公式解可以通过正规方程得到:

      θ=(XTX)1XTy

      其中:

      • θ 是包含截距和各特征系数的参数向量。
      • X 是特征矩阵 (每一行是一个样本,每一列是一个特征,通常第一列为全1以对应截距项)。
      • y 是目标值向量。
      • (XTX)1 是矩阵 XTX 的逆。
    • 矩阵求逆的复杂度: 计算 (XTX)1 这一步的计算复杂度大约是 O(n3),其中 n 是特征的数量。当特征数量 n 非常大时(例如,成千上万甚至更多),计算这个逆矩阵会变得非常缓慢,甚至在计算上不可行。

  2. 内存需求大 (Large Memory Requirement):

    • 当特征数量 n 很大时,XTX 是一个 n×n 的矩阵。存储这个矩阵以及进行求逆运算需要大量的内存。
  3. 不适用于所有类型的模型 (Not Applicable to All Model Types):

    • 正规方程是线性回归的解析解。对于更复杂的模型,如逻辑回归(用于分类)或神经网络,通常不存在这样简单的闭式解(closed-form solution)。这些模型必须依赖迭代优化算法(如梯度下降)来学习参数。
  4. 迭代方法的优势 (Advantages of Iterative Methods like Gradient Descent):

    • 可扩展性好 (Scalability): 梯度下降等迭代算法在特征数量非常大时,其计算成本(每次迭代)通常低于正规方程。对于梯度下降,每次迭代的计算复杂度大约是 O(knm),其中 k 是迭代次数,n 是特征数,m 是样本数。在许多实际情况下,梯度下降可以更快地找到一个足够好的解。
    • 适用于大数据集 (Works well with large datasets): 随机梯度下降 (SGD) 及其变种(如 Mini-batch SGD)可以一次只处理一部分数据来更新参数,这使得它们在数据集非常大,无法一次性载入内存时非常有效。
    • 更广泛的适用性 (Broader Applicability): 梯度下降是许多机器学习算法(不仅仅是线性回归)的核心优化方法。

什么时候公式法(正规方程)仍然适用?

尽管存在上述限制,但在以下情况下,公式法仍然是一个不错的选择:

  • 特征数量较少时 (Small Number of Features): 当特征数量 n 相对较小(例如,小于1000或几千,具体取决于计算资源),计算矩阵的逆是可行的,并且可以直接得到精确解,无需选择学习率或担心迭代次数。
  • 当需要精确解且计算资源允许时: 如果对解的精度要求非常高,并且计算资源不是瓶颈。

总结来说:

机器学习中可以使用公式法求解线性回归。但是,由于计算复杂度和内存的限制,当特征数量非常大时,正规方程会变得不切实际。在这种情况下,梯度下降等迭代方法因其更好的可扩展性和效率而成为更常用的选择。因此,不是“不能用”,而是“在很多实际的机器学习场景下,有更合适或更高效的方法”。 您提出的问题非常好,触及了理论与实践之间的一个重要考量。虽然统计学中的线性回归公式(通常指正规方程,Normal Equation)能够直接给出最优解,但在机器学习的实际应用中,并非总是首选,主要原因如下:

1. 计算成本与可扩展性 (Computational Cost and Scalability):

  • 正规方程涉及矩阵求逆: 正规方程的公式是 θ=(XTX)1XTy。其中,关键步骤是计算 (XTX)1,即对矩阵 XTX 进行求逆操作。
    • X 是特征矩阵,维度为 m×nm 是样本数量,n 是特征数量)。
    • XTX 的维度是 n×n
  • 矩阵求逆的计算复杂度: 矩阵求逆的计算复杂度大约是 O(n3),其中 n 是特征的数量。
    • 当特征数量 n 非常大时,计算 (XTX)1 会变得极其缓慢且消耗大量计算资源。 例如,如果一个数据集有数万甚至数百万个特征(这在现代机器学习问题中并不少见,如图像处理、文本分析等),那么 n3 将是一个天文数字,使得直接求解变得不可行。
  • 梯度下降的计算成本: 相比之下,梯度下降 (Gradient Descent) 是一种迭代优化算法。
    • 在每次迭代中,梯度下降的计算复杂度主要取决于样本数量 m 和特征数量 n(通常是 O(mn) 来计算梯度)。
    • 虽然梯度下降需要多次迭代才能收敛,但当特征数量 n 巨大时,即使迭代多次,其总计算量也可能远小于正规方程的 O(n3)

2. 内存占用 (Memory Usage):

  • 正规方程需要将整个特征矩阵 X 加载到内存中以计算 XTX。如果数据集非常大(样本数量 m 巨大),即使特征数量 n 不算特别大,也可能导致内存不足的问题。
  • 梯度下降的某些变种,如随机梯度下降 (Stochastic Gradient Descent, SGD) 或小批量梯度下降 (Mini-batch Gradient Descent),可以一次只处理一个或一小批样本来更新参数。这使得它们在处理无法完全加载到内存中的大规模数据集时非常有效(即所谓的 "在线学习" 或 "核外学习")。

3. 迭代优化算法的通用性 (Generality of Iterative Optimization):

  • 梯度下降不仅适用于线性回归,它是一种通用的优化算法,可以应用于各种机器学习模型,包括逻辑回归、神经网络等更复杂的模型。这些模型通常没有像线性回归那样的封闭解(即直接求解的公式)。
  • 因此,在机器学习领域,掌握和使用梯度下降这类迭代优化方法具有更广泛的适用性。

4. 正则化等高级技术的集成 (Integration with Advanced Techniques like Regularization):

  • 虽然正规方程也可以结合正则化(例如岭回归的解析解),但梯度下降更容易与各种正则化方法(L1, L2等)以及其他优化技巧灵活地结合。

5. 数值稳定性 (Numerical Stability):

  • 当矩阵 XTX 是奇异矩阵(不可逆)或接近奇异(病态条件,ill-conditioned)时,直接计算其逆可能会导致数值不稳定或不准确的结果。这种情况可能在特征之间存在高度共线性时发生。
  • 虽然梯度下降也可能遇到数值问题,但通常有更多的方法来缓解这些问题,例如通过特征缩放、调整学习率等。

总结一下,何时可以考虑使用公式法(正规方程):

  • 特征数量 n 相对较小 (例如,Andrew Ng 教授在他的课程中建议 n 小于 10000 作为一个经验法则,但具体阈值取决于可用的计算资源)。
  • 数据集可以完全加载到内存中。
  • 不需要迭代调参(如学习率),可以直接得到最优解。
  • 线性回归是最终模型,不需要考虑将优化算法推广到其他更复杂的模型。

在以下情况下,梯度下降通常是更好的选择:

  • 特征数量 n 非常大。
  • 数据集规模庞大,无法一次性加载到内存。
  • 需要使用更复杂的模型(如神经网络),这些模型没有直接的解析解。
  • 需要灵活地集成正则化等技术。

因此,机器学习中虽然理论上存在直接求解线性回归的公式,但出于对计算效率、内存限制、算法通用性和处理大规模高维数据的实际需求,迭代优化算法(如梯度下降及其变种)往往是更常用和更实用的选择。

最后更新于:

Released under the MIT License.